import axios, { AxiosInstance } from 'axios'
import {
  showLoading,
  hideLoading,
  ajaxError,
} from '@/commponents/common/GlobalModal'
import type { RequestInterceptors, RequestConfig } from './type'

// axios的错误状态处理
import axiosErrorHandler from './axiosErrorHandler'

// 是否显示loading 全局默认显示loading
const DEAFULT_LOADING = true
// 接口状态这块，我们主要是解决一些统一的属性，所以，正常情况下我们会处理所有的接口错误
const DEAFULT_HANDLER_STATUS = true

/**
 * 关于拦截器
 * 一般来说，在企业，同一个项目对应的可能会有多台接口服务器，有的服务器管理用户信息，
 * 有的服务器管理数据存储，还有的服务器进行数据分析和计算；这些服务器地址不同，配置
 * 也不同，然后返回的状态值与消息格式也因为种种原因不一致，所以，我们在真实项目中往
 * 往需要配置多个拦截器以方便适配各台接口服用器。
 *
 * 拦截器我们要使用axios的use方法进行注册，而use方法的执行顺序是先进后出的顺序，也
 * 就是说，先注册的后执行，后注册的先执行
 *
 * 期望的请求拦截器的执行顺序：
 * 统一拦截器 => 实例拦截器 => 单个请求拦截器
 * 为什么需要这个顺序呢？我们可能会在统一的拦截器里设置超时时长为3000，但某个实例可能
 * 因为服务器原因，3000无法获取到数据，需要调整到5000；所以我们需要使用实例的配置覆盖
 * 统一的配置；单个请求的也是这样。
 *
 * 期望的响应拦截器的执行顺序：
 * 统一拦截器 => 实例拦截器 => 单个请求拦截器
 * 统一拦截器用于处理axios的状态，然后对数据进行脱壳，就是将axios的状态包裏去掉，只返回
 * api服务器给我们的内容
 * 实例拦截器，一般来说，实例拦截器会处理一些当前服务器的错误状态，比如未登陆或一些特殊页
 * 面无权限访问等，处理完成后，再次抛弃接口的状态包裹，这样组件中就可以直接使用数据了
 */

export class Request {
  // axios的实例
  instance: AxiosInstance
  // 拦截器，为了更好地实现拦截器功能并返回正确数据，这里使用了自定义类型，具体可以参考type文件
  interceptors?: RequestInterceptors
  // 是否统一展示loading
  showLoading: boolean
  // 是否统一处理超时等
  handlerStatus: boolean

  // 实例化时传入的配置参数
  constructor(config: RequestConfig) {
    // 创建axios实例
    this.instance = axios.create(config)

    // 注意：双问号的用法等同于三元表达式，但是它不会对变量进行转换，也就是说，传入false或null也代表着已经赋值
    // 例如： a = false; b = a ?? true; log(b) // false
    // 这里可以看到，以实例传入的配置优先，实例化时没有传入配置则使用默认的配置

    // 是否需要统一显示loading
    this.showLoading = config.showLoading ?? DEAFULT_LOADING
    // 是否统一处理接口返回的错误
    this.handlerStatus = config.handlerStatus ?? DEAFULT_HANDLER_STATUS

    // 将实例化时传入的拦截器，保存一下
    this.interceptors = config.interceptors

    // 注册实例的请求拦截器
    this.instance.interceptors.request.use(
      this.interceptors?.requestInterceptor,
      this.interceptors?.requestCatch
    )

    // 注册统一拦截
    this.instance.interceptors.request.use(
      config => {
        console.log(
          '统一的请求拦截，它应该是最先执行的，我们在这里进行一个全局的请求配置: ',
          config
        )
        if (this.showLoading) {
          showLoading({
            from: 'axios',
            text: '数据加载中...',
          })
        }
        return config
      },
      err => {
        // 注意，这里必须使用promise.reject，否则错误数据会被传到一下个拦截器的正确请求里
        return Promise.reject(err)
      }
    )

    // 所有实例的响应拦截
    // 注意：响应拦截的注册顺序与请求拦截一致，但执行顺序不一样，它是由前至后的，也就是说，谁先注册先执行谁
    this.instance.interceptors.response.use(
      res => {
        console.log('统一的响应拦截', res)
        // 如果有显示loading 这里需要隐藏它
        this.showLoading && hideLoading()
        // 这里只处理请求成功后去除axios状态等封包的状态
        if (res.status === 200 && res.statusText === 'OK') {
          return res.data
        }
        // 非200的都视为错误
        console.log(
          '请求失败，地址: 错误代码: %s; 错误消息: %s',
          res.status,
          res.statusText
        )
        ajaxError<string>([res.statusText])
        // 这里返回reject，直接调用失败
        return Promise.reject()
      },
      err => {
        this.showLoading && hideLoading()
        // 直接处理错误
        const error = axiosErrorHandler(err)
        console.log(err)
        console.log(
          '请求失败，地址: 错误代码: %s; 错误消息: %s',
          error.status,
          error.errorMessage
        )
        ajaxError<string>(error.errorMessage)
        // 直接返回给下一级失败，
        return Promise.reject()
      }
    )

    // 单个实例的响应拦截，上面的统一拦截器处理完成后会将处理后的结果传给这个拦截器
    this.instance.interceptors.response.use(
      this.interceptors?.responseInterceptor,
      this.interceptors?.responseCatch
    )
  }

  /**
   * 针对单个请求的处理、拦截
   * 为了添加对单个请求的拦截 使用扩展的 RequestConfig 替换 AxiosRequestConfig
   * return Promise的话就需要将函数的返回值改为Promise，Promise需要传入一个泛型，我们定义为T
   * @param config
   * @returns
   */
  request<T>(config: RequestConfig<T>): Promise<T> {
    // T是泛型 ，相当于一个参数 使用这个函数的人传这个参数来决定类型
    return new Promise((resolve, reject) => {
      // 单个请求的拦截器，现在还有问题，这块暂时先忽略！！！！
      if (config.interceptors?.requestInterceptor) {
        //如果有单独设置的请求拦截
        config = config.interceptors.requestInterceptor(config) //config改为拦截处理后的config
      }
      // 调用axios实例
      this.instance
        // request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>;
        // 改为T类型后就需要来这里修改泛型 原本T的位置是AxiosResponse 相应的 应该去到type.ts文件将对应的类型修改 详见type.ts文件
        // axios request
        .request<any, T>(config)
        .then(res => {
          // console.log(res, '$$$$$$$$$$这里是成功')
          // 单个请求完后的响应拦截，同请求拦截，暂时先忽略
          if (config.interceptors?.responseInterceptor) {
            // 使用拦截器处理过后再返回
            res = config.interceptors.responseInterceptor(res)
          }
          resolve(res)
        })
        .catch(err => {
          // console.log(err, '##########这里是错误')
          reject(err)
          return err
        })
    })
  }
  /**
   * 向外暴露的简易方法
   * 调用时需要传入返回类型
   * @param url
   * @param config
   * @returns
   */
  post<T>(url: string, data: any, config?: RequestConfig<T>): Promise<T> {
    // 需要给RequestConfig传入T类型 否则是默认AxiosResponse类型 与实际不匹配
    return this.request<T>({ ...config, data, url, method: 'POST' })
  }
  put<T>(url: string, data: any, config?: RequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, data, url, method: 'PUT' })
  }
  get<T>(url: string, data?: any, config?: RequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, params: data, url, method: 'GET' })
  }
  delete<T>(url: string, data?: any, config?: RequestConfig<T>): Promise<T> {
    return this.request<T>({ ...config, params: data, url, method: 'DELETE' })
  }
}
